﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Kotonoha.Objects;
using Kotonoha.Server.Objects.Model;
using Kotonoha.Server.Objects.Model.Event;
using Kotonoha.Server.Core.Utils;
using Kotonoha.Server.Core.Xml;
using Kotonoha.Server.Utils;
using NHibernate;
using OpenNETCF.Desktop.Communication;
using Kotonoha.Server.Extensions;

namespace Kotonoha.Server.Synch {
    public class Synchronizer {
        private readonly WordHolder holder;

        public Synchronizer(WordHolder holder) {
            this.holder = holder;
        }

        public bool synchronize() {
            RAPI rapi = new RAPI();
            try {
                rapi.Connect(true, 5);
                return synchDb(rapi);
            } finally {
                rapi.Disconnect();
                rapi.Dispose();
            }
        }

        private bool synchDb(RAPI rapi) {
            CERegistryKey key = CERegistry.CurrentUser.OpenSubKey(@"Software\Kotonoha");
            if (key != null) {
                string install = (string) key.GetValue("InstallDir");
                markStart(install, rapi);
                synchData(install, rapi);
                synchDb(install, rapi);
                markEnd(install, rapi);
                return true;
            }
            return false;
        }

        private void synchData(string install, RAPI rapi) {
            string datadir = Path.Combine(install, "data");
            string log = Path.Combine(datadir, "log");

            if (rapi.DeviceFileExists(log)) {
                string local = Path.Combine(PathUtil.USER_DATA_DIR, "log");
                rapi.CopyFileFromDevice(local, log, true);
                rapi.DeleteDeviceFile(log);

                mergeLogFile(local);
                File.Delete(local);
            }
        }

        private void mergeLogFile(string local) {
            EventList list = new EventList(local);
            IEnumerable<Event> events = list.load();
            ISession session = HibernateUtil.Model.Session;
            ITransaction transaction = session.BeginTransaction();
            try {
                events.ForEach(e => e.apply(holder));
                events.ForEach(e => session.Save(e));
                transaction.Commit();
            } catch {
                transaction.Rollback();
                throw;
            }
        }

        private void markEnd(string install, RAPI rapi) {
            string begin = getBeginTokenPath(install);
            string end = Path.Combine(install, "synch_token_end");
            if (rapi.DeviceFileExists(begin)) {
                rapi.DeleteDeviceFile(begin);
            }

            if (rapi.DeviceFileExists(end)) {
                rapi.DeleteDeviceFile(end);
            }

            string temp = Path.GetTempFileName();
            FileStream write = File.OpenWrite(temp);
            write.WriteByte(255);
            write.Close();
            rapi.CopyFileToDevice(temp, end);
            File.Delete(temp);
        }

        private void markStart(string install, RAPI rapi) {
            string path = getBeginTokenPath(install);
            string tempFile = Path.GetTempFileName();
            FileStream write = File.OpenWrite(tempFile);
            write.WriteByte(255);
            write.Close();
            rapi.CopyFileToDevice(tempFile, path, true);
            File.Delete(tempFile);
        }

        private string getBeginTokenPath(string install) {
            const string start_name = "synch_token_begin";
            return Path.Combine(install, start_name);
        }

        private void synchDb(string install, RAPI rapi) {
            string datadir = Path.Combine(install, "data");
            string db = Path.Combine(datadir, "db.xml");
            if (!rapi.DeviceFileExists(datadir)) {
                rapi.CreateDeviceDirectory(datadir);
            }
            if (rapi.DeviceFileExists(db)) {
                rapi.DeleteDeviceFile(db);
            }
            string tempDb = Path.GetTempFileName();
            FileStream write = File.OpenWrite(tempDb);
            ContainerSerializer ser = new ContainerSerializer();
            ser.Serialize(write, ContainerFnc());
            write.Close();

            rapi.CopyFileToDevice(tempDb, db, true);
            File.Delete(tempDb);
        }

        public Func<Container> ContainerFnc { get; set; }
    }
}